/**
 * Copyright (C) 2009 fgrilli <federico.grilli@gmail.com>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 */
package com.google.code.mgnlgroovy.scheduler.admin;

import info.magnolia.cms.beans.config.ContentRepository;
import info.magnolia.cms.beans.runtime.Document;
import info.magnolia.cms.core.Content;
import info.magnolia.cms.core.search.Query;
import info.magnolia.cms.core.search.QueryManager;
import info.magnolia.cms.core.search.QueryResult;
import info.magnolia.cms.util.AlertUtil;
import info.magnolia.context.MgnlContext;
import info.magnolia.module.admininterface.ValidatableMVCHandler;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.jcr.ItemExistsException;
import javax.jcr.ItemNotFoundException;
import javax.jcr.RepositoryException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.chain.CatalogFactory;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.ArrayUtils;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.code.mgnlgroovy.scheduler.JobDefinition;
import com.google.code.mgnlgroovy.scheduler.manager.JobDefinitionManager;


/**
 * @author fgrilli
 * @version $Id: SchedulerEditJob.java 3 2009-04-16 22:48:19Z federico.grilli $
 */
public class SchedulerEditJob extends ValidatableMVCHandler
{

    protected static final Logger log = LoggerFactory.getLogger(SchedulerEditJob.class);

    private static final String LINE_SEP = System.getProperty("line.separator");

    private JobDefinition job = new JobDefinition();

    private JobDefinitionManager jobDefinitionManager = JobDefinitionManager.Factory.getInstance();

    private boolean newJobDefinition = false;

    private boolean editJobDefinition = false;

    private boolean deleteJobDefinition = false;

    private String UUID;

    private Document mgnlFileImport;

    private Map<String, Set<String>> commands = findCommandNames();

    private String jobType;

    public SchedulerEditJob(String name, HttpServletRequest request, HttpServletResponse response)
    {
        super(name, request, response);
    }

    public String newJob()
    {
        log.info("creating new job");
        return this.show();
    }

    public String editJob()
    {
        try
        {
            job = jobDefinitionManager.getJobDefinitionByUUID(UUID);
            log.info("editing job {}", job.getName());
        }
        catch (ItemNotFoundException e)
        {
            log.error(e.getMessage());
        }
        catch (RepositoryException e)
        {
            log.error(e.getMessage());
        }

        return this.show();
    }

    public String deleteJob()
    {
        try
        {
            editJobDefinition = false;
            newJobDefinition = false;
            deleteJobDefinition = true;
            job = jobDefinitionManager.getJobDefinitionByUUID(UUID);
            log.info("deleting job {}", job.getName());
            jobDefinitionManager.removeJobDefinition(UUID);
            AlertUtil.setMessage("Job <strong>" + job.getName() + "</strong> deleted successfully");
        }
        catch (ItemNotFoundException e)
        {
            log.error(e.getMessage());
            AlertUtil.setException(e.getMessage(), e);
        }
        catch (RepositoryException e)
        {
            log.error(e.getMessage());
            AlertUtil.setException(e.getMessage(), e);
        }
        return this.show();
    }

    public String updateJob()
    {
        try
        {
            jobDefinitionManager.saveOrUpdateJobDefinition(job);
            AlertUtil.setMessage("Job updated successfully");
        }
        catch (ItemExistsException e)
        {
            log.error(e.getMessage());
            AlertUtil.setException(e.getMessage(), e);
        }
        catch (RepositoryException e)
        {
            log.error(e.getMessage());
            AlertUtil.setException(e.getMessage(), e);
        }

        return this.show();
    }

    public String createJob()
    {
        try
        {
            if (StringUtils.isNotBlank(job.getGroovyScript()))
            {
                job.setCatalog(null);
                job.setCommand(null);
            }

            UUID = jobDefinitionManager.saveOrUpdateJobDefinition(job);
            job = jobDefinitionManager.getJobDefinitionByUUID(UUID);
            AlertUtil.setMessage("Job created successfully");
            newJobDefinition = false;
            editJobDefinition = true;
            setCommand("editJob");
        }
        catch (ItemExistsException e)
        {
            log.error(e.getMessage());
            AlertUtil.setException(e.getMessage(), e);
        }
        catch (RepositoryException e)
        {
            log.error(e.getMessage());
            AlertUtil.setException(e.getMessage(), e);
        }

        return this.show();
    }

    public String getCatalog()
    {
        return job.getCatalog();
    }

    public String getCommandName()
    {
        return job.getCommand();
    }

    public String getCron()
    {
        return job.getCron();
    }

    public Long getEndTime()
    {
        return job.getEndTime();
    }

    public String getGroovyScript()
    {
        return job.getGroovyScript();
    }

    public String getJobName()
    {
        return job.getName();
    }

    public Map getJobParams()
    {
        log.debug("getJobParams {}", job.getParams());
        return job.getParams();
    }

    public String getJobParamsAsString()
    {
        return ArrayUtils.toString(job.getParams());
    }

    public Long getStartTime()
    {
        return job.getStartTime();
    }

    public boolean isActive()
    {
        return job.isActive();
    }

    public boolean isTerminatedWithError()
    {
        return job.isTerminatedWithError();
    }

    public void setActive(boolean active)
    {
        job.setActive(active);
    }

    public void setCatalog(String catalogue)
    {
        job.setCatalog(catalogue);
    }

    public void setCommandName(String command)
    {
        job.setCommand(command);
    }

    public void setCron(String cron)
    {
        job.setCron(cron);
    }

    public void setEndTime(Long endTime)
    {
        job.setEndTime(endTime);
    }

    public void setGroovyScript(String groovyScript)
    {
        job.setGroovyScript(groovyScript);
    }

    public void setJobName(String name)
    {
        job.setName(name);
    }

    public void setJobParams(String params)
    {
        if (StringUtils.isEmpty(params))
            return;
        String[] keyValuePairs = params.split(";");
        Map<String, String> map = new LinkedHashMap<String, String>();
        for (String keyValue : keyValuePairs)
        {
            String[] pair = keyValue.split(":");
            map.put(pair[0], pair[1]);
        }
        job.setParams(map);
    }

    public void setStartTime(Long startTime)
    {
        job.setStartTime(startTime);
    }

    public void setTerminatedWithError(boolean terminatedWithError)
    {
        job.setTerminatedWithError(terminatedWithError);
    }

    public String getUUID()
    {
        return UUID;
    }

    public void setUUID(String uuid)
    {
        UUID = uuid;
    }

    public boolean isNewJobDefinition()
    {
        return newJobDefinition;
    }

    public void setNewJobDefinition(boolean newJobDefinition)
    {
        this.newJobDefinition = newJobDefinition;
    }

    public boolean isEditJobDefinition()
    {
        return editJobDefinition;
    }

    public void setEditJobDefinition(boolean editJobDefinition)
    {
        this.editJobDefinition = editJobDefinition;
    }

    public boolean isDeleteJobDefinition()
    {
        return deleteJobDefinition;
    }

    public void setDeleteJobDefinition(boolean deleteJobDefinition)
    {
        this.deleteJobDefinition = deleteJobDefinition;
    }

    public String getDescription()
    {
        return job.getDescription();
    }

    public void setDescription(String description)
    {
        job.setDescription(description);
    }

    public Document getMgnlFileImport()
    {
        return mgnlFileImport;
    }

    public void setMgnlFileImport(Document mgnlFileImport)
    {
        this.mgnlFileImport = mgnlFileImport;
    }

    public Long getNextFireTime()
    {
        return job.getNextFireTime();
    }

    public void setNextFireTime(Long nextFireTime)
    {
        job.setNextFireTime(nextFireTime);
    }

    public void setJobType(String jobType)
    {
        this.jobType = jobType;
    }

    public String getJobType()
    {
        return jobType;
    }

    public String loadGroovyScript()
    {
        if (mgnlFileImport == null)
        {
            String msg = "Please, select a file";
            log.warn(msg);
            AlertUtil.setException(msg, new Exception());
            return this.show();
        }
        if (!mgnlFileImport.getExtension().equalsIgnoreCase("groovy"))
        {
            String msg = mgnlFileImport.getFileNameWithExtension() + " doesn't seem to be a valid groovy file";
            log.warn(msg);
            AlertUtil.setException(msg, new Exception());
            return this.show();
        }
        job.setGroovyScript(readFile(mgnlFileImport.getFile()));

        String msg = mgnlFileImport.getFileNameWithExtension() + " loaded";
        log.info(msg);
        AlertUtil.setMessage(msg);
        return this.show();

    }

    private String readFile(File file)
    {
        StringBuilder contents = new StringBuilder();

        try
        {
            // use buffering, reading one line at a time
            // FileReader always assumes default encoding is OK!
            BufferedReader input = new BufferedReader(new FileReader(file));
            try
            {
                String line = null;
                /*
                 * readLine is a bit quirky : it returns the content of a line MINUS the newline. it returns null only
                 * for the END of the stream. it returns an empty String if two newlines appear in a row.
                 */
                while ((line = input.readLine()) != null)
                {
                    contents.append(line);
                    contents.append(LINE_SEP);
                }
            }
            finally
            {
                IOUtils.closeQuietly(input);
            }
        }
        catch (IOException ex)
        {
            log.error(ex.getMessage());
        }
        return contents.toString();
    }

    @SuppressWarnings("unchecked")
    private Map<String, Set<String>> findCommandNames()
    {
        QueryManager qm = MgnlContext.getQueryManager(ContentRepository.CONFIG);
        Iterator catalogIter = CatalogFactory.getInstance().getNames();
        Map<String, Set<String>> retVal = new HashMap<String, Set<String>>();
        while (catalogIter.hasNext())
        {
            String catalog = (String) catalogIter.next();
            try
            {
                Query q = qm.createQuery("//commands//" + catalog + "//*", Query.XPATH);
                QueryResult qr = q.execute();
                Collection collection = qr.getContent("mgnl:contentNode");
                if (CollectionUtils.isEmpty(collection))
                    continue;
                Iterator commandIter = collection.iterator();
                Set<String> commands = new HashSet<String>();
                while (commandIter.hasNext())
                {
                    Content content = (Content) commandIter.next();
                    commands.add(content.getName());
                }
                retVal.put(catalog, commands);
            }
            catch (RepositoryException e)
            {
                log.warn(e.getMessage());
                throw new RuntimeException(e);
            }
        }
        return retVal;
    }

    public void setCommands(Map<String, Set<String>> commands)
    {
        this.commands = commands;
    }

    public Map<String, Set<String>> getCommands()
    {
        return commands;
    }

    @Override
    public boolean validate(String command, List<Exception> errors)
    {
        if (command.equals("newJob") || command.equals("editJob") || command.equals("deleteJob"))
            return true;
        // TODO this is a workaround, as long as I will understand
        // how to populate automatically a Map from an http request parameter with BeanUtils
        setJobParams(getRequest().getParameter("jobParams"));

        log.debug("command is {}", command);

        if (!checkMandatoryFields(errors))
            return false;

        if ("updateJob".equals(command))
        {
            log.debug("validating update");
            if (getCommandName() == null && StringUtils.isBlank(getGroovyScript()))
            {
                String msg = "groovy script cannot be blank";
                log.warn(msg);
                errors.add(new Exception(msg));
                return false;
            }

        }

        else if ("createJob".equals(command))
        {
            log.debug("validating new job");

            if ("groovyJob".equals(getJobType()) && StringUtils.isBlank(getGroovyScript()))
            {
                String msg = "groovy script cannot be blank";
                log.warn(msg);
                errors.add(new Exception(msg));
                return false;
            }
            if (jobDefinitionManager.sameJobNameExists(getJobName()))
            {
                String msg = "job <strong>" + getJobName() + "</strong> already exists. Please choose another name.";
                log.warn(msg);
                errors.add(new Exception(msg));
                return false;
            }
        }
        if (getEndTime() != 0 && (getStartTime() > getEndTime()))
        {
            String msg = "Start time cannot be after end time";
            log.warn(msg);
            errors.add(new Exception(msg));
            return false;
        }
        return true;
    }

    private boolean checkMandatoryFields(List<Exception> errors)
    {
        if (StringUtils.isBlank(getJobName()))
        {
            String msg = "job name cannot be blank";
            log.warn(msg);
            errors.add(new Exception(msg));
        }
        if (StringUtils.isBlank(getCron()))
        {
            String msg = "cron expression cannot be blank";
            log.warn(msg);
            errors.add(new Exception(msg));
        }
        if ("groovyJob".equals(getJobType()) && StringUtils.isBlank(getGroovyScript()))
        {
            String msg = "groovy script cannot be blank";
            log.warn(msg);
            errors.add(new Exception(msg));
        }
        if (isActive() && getStartTime() == 0)
        {
            String msg = "Please, choose a start time or set your job as inactive.";
            log.warn(msg);
            errors.add(new Exception(msg));
        }
        return errors.isEmpty();
    }
}